﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.WowAddonStudio.FrameXml.Formats.Mpq;

namespace Microsoft.WowAddonStudio.FrameXml.Services
{
	public class ResourceFileLoader : IDisposable
	{
        private static readonly string[] basePathFolders = new[] { "Interface" };
        private const string listfilePath = "(listfile)";

		private List<string> basePaths = new List<string>(1);
		private List<MpqArchive> mpqArchives = new List<MpqArchive>(1);

		private bool disposed;

		/// <summary>
		/// Gets the base paths that the <see cref="ResourceFileLoader"/> tries to load from.
		/// </summary>
		public virtual List<string> BasePaths
		{
			get
			{
				if (disposed)
					throw new ObjectDisposedException("ResourceFileLoader");

				return basePaths;
			}
		}

		/// <summary>
		/// Gets the list of MPQ archives to be searched.
		/// </summary>
		public virtual List<MpqArchive> MpqArchives
		{
			get
			{
				if (disposed)
					throw new ObjectDisposedException("ResourceFileLoader");

				return mpqArchives;
			}
		}

		/// <summary>
		/// Checks whether a resource file exists.
		/// </summary>
		/// <param name="resourceFileRelativePath">The relative path of the resource file.</param>
		/// <returns>True, if the resource file exists; False otherwise.</returns>
		public virtual bool ResourceFileExists(string resourceFileRelativePath)
		{
			if (resourceFileRelativePath == null)
				throw new ArgumentNullException("resourceFileRelativePath");
			if (disposed)
				throw new ObjectDisposedException("ResourceFileLoader");

			// Check physical base paths for resource file
			foreach (var basePath in basePaths)
			{
				string resourceFilePath = Path.Combine(basePath, resourceFileRelativePath);

				// Check for phsyical path
				if (File.Exists(resourceFilePath))
					return true;
			}

			// Check MPQ archives for resource file
			foreach (var archive in mpqArchives)
			{
				// Check for the file in the archive
				if (archive.FileExists(resourceFileRelativePath))
					return true;
			}

			return false;
		}

		/// <summary>
		/// Gets a resource file by resolving the path to either a physical path in the WoW folder or inside an MPQ archive.
		/// </summary>
		/// <param name="resourceFileRelativePath">The relative path of the resource file.</param>
		/// <returns>An instance of the <see cref="Stream"/> class.</returns>
		/// <remarks>The stream must be disposed after use.</remarks>
		public virtual Stream GetResourceFileStream(string resourceFileRelativePath)
		{
			if (resourceFileRelativePath == null)
				throw new ArgumentNullException("resourceFileRelativePath");
			if (disposed)
				throw new ObjectDisposedException("ResourceFileLoader");

			// Check physical base paths for resource file
			foreach (var basePath in basePaths)
			{
				string resourceFilePath = Path.Combine(basePath, resourceFileRelativePath);

				// Check for phsyical path
				if (File.Exists(resourceFilePath))
					return new FileStream(resourceFilePath, FileMode.Open, FileAccess.Read);
			}

			// Check MPQ archives for resource file
			foreach (var archive in mpqArchives)
			{
				// Check for the file in the archive
				if (archive.FileExists(resourceFileRelativePath))
				{
					// File exists, open the file and return the stream
					MpqFile mpqFile = archive.OpenFile(resourceFileRelativePath);
					return mpqFile.GetStream();
				}
			}

			return null;
		}

		/// <summary>
		/// Merges list file contents from all archives.
		/// </summary>
		/// <returns>Merged list of list file contents.</returns>
        public virtual IEnumerable<ResourceFile> GetResourceFiles()
		{
            var result = new List<ResourceFile>();

			foreach (var archive in mpqArchives)
			{
				// Check for the list file in the archive
				if (archive.FileExists(listfilePath))
				{
					// File exists, open the file and return the stream
					MpqFile mpqFile = archive.OpenFile(listfilePath);

					using (var reader = new StreamReader(mpqFile.GetStream()))
					{
						try
						{
							while (!reader.EndOfStream)
							{
								result.Add(new ResourceFile(reader.ReadLine(), ResourceFileType.Mpq));
							}
						}
						catch (Exception ex)
						{
							Trace.WriteLine(ex);
						}
					}
				}
			}

            foreach (string basePath in basePaths)
            {
                foreach (string folder in basePathFolders)
                {
                    var files = Directory.GetFiles(Path.Combine(basePath, folder), "*.*", SearchOption.AllDirectories).Select(file => file.Substring(basePath.Length).Trim('\\'));

                    foreach (string file in files)
                    {
                        result.Add(new ResourceFile(file, ResourceFileType.AddOn));
                    }
                }
            }

		    return result.Distinct();
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!disposed && disposing)
			{
				if (mpqArchives != null)
				{
					foreach (var mpqArchive in mpqArchives)
						mpqArchive.Dispose();

					basePaths = null;
					mpqArchives = null;
				}

				disposed = true;
			}
		}
	}
}